package operator

import (
	"context"
	"net/http"

	"github.com/viant/datly/repository"
	"github.com/viant/datly/service/reader"
	"github.com/viant/datly/service/reader/handler"
	"github.com/viant/datly/service/session"
	"github.com/viant/datly/view"
	"github.com/viant/xdatly/handler/async"
	"github.com/viant/xdatly/handler/response"

	"fmt"
	"runtime/debug"
	"time"
)

func (s *Service) runQuery(ctx context.Context, component *repository.Component, aSession *session.Session) (output interface{}, err error) {
	//TODO handler async
	var handlerResponse *handler.Response
	defer func() {
		if r := recover(); r != nil {
			panicMsg := fmt.Sprintf("Panic occurred: %v, Stack trace: %v", r, string(debug.Stack()))
			logger := aSession.Logger()
			if logger == nil {
				panic(panicMsg)
			}
			aSession.Logger().Errorc(ctx, panicMsg)
			err = response.NewError(http.StatusInternalServerError, "Internal server error")
			output = nil
		}
	}()

	readerHandler := handler.New(component.Output.Type.Type(), &component.Output.Type)
	var options = []reader.Option{
		reader.WithCacheDisabled(false),
	}
	startTime := time.Now()
	s.adjustAsyncOptions(ctx, aSession, component.View, &options)
	handlerResponse = readerHandler.Handle(ctx, component.View, aSession, options...)
	setting := aSession.State().QuerySettings(component.View)
	if err := s.updateJobStatusDone(ctx, component, handlerResponse, setting.SyncFlag, startTime); err != nil {
		return nil, err
	}
	if output, err = s.finalize(ctx, handlerResponse.Output, handlerResponse.Error, aSession); err != nil {
		aSession.ClearCache(component.Output.Type.Parameters)
		return s.HandleError(ctx, aSession, component, err)
	}
	return output, err
}

// adjustAsyncOptions function adjust reading option to dryRun when asyb job is scheduled but not yet completed
func (s *Service) adjustAsyncOptions(ctx context.Context, aSession *session.Session, aView *view.View, options *[]reader.Option) {
	if job := s.Job(ctx); job != nil {
		setting := aSession.State().QuerySettings(aView)
		if s.IsEventInvocation(ctx) {
			//Makes sure cache is always refreshed
			*options = append(*options, reader.WithCacheRefresh())
		} else if async.Status(job.Status) != async.StatusDone || setting.SyncFlag {
			//Make sure not actual database is used
			if setting.SyncFlag { //sync flag would perform regular read
				*options = append(*options, reader.WithCacheRefresh())
			} else {
				*options = append(*options, reader.WithDryRun())
			}
		}
	}
}

func (s *Service) InvocationType(ctx context.Context) async.InvocationType {
	if value := ctx.Value(async.InvocationTypeKey); value != nil {
		ret, ok := value.(async.InvocationType)
		if ok {
			return ret
		}
	}
	return async.InvocationTypeUndefined
}

func (s *Service) Job(ctx context.Context) *async.Job {
	if value := ctx.Value(async.JobKey); value != nil {
		ret, ok := value.(*async.Job)
		if ok {
			return ret
		}
	}
	return nil
}
